//
// Created by cube on 2020/10/22.
//

#include "Multplex.h"

using namespace NetBase;

//Multplex==========================================================================

//Select Multplex

int Select_Multplex::server(long timeout) {
    std::set<int> sockets;
    if(true){
        std::lock_guard l(__mutex);
        sockets = __sockets;
    }

    FD_ZERO(&fd_read);
//        FD_ZERO(&fd_write);
//        FD_ZERO(&fd_exp);

    for (auto p = sockets.begin();p != sockets.end();p++)
        FD_SET(*p,&fd_read);

    timeval t = {timeout,0};
    int rul = select(__select_socket+1,&fd_read, nullptr
            , nullptr, ((timeout<0)?nullptr : &t));

    if(rul > 0){
        for(auto p = sockets.begin();p != sockets.end();p++){
            int sock = *p;
            if(FD_ISSET(sock,&fd_read)){
                FD_CLR(sock,&fd_read);
                if(!read(sock)){
                    disConnect(sock);
                    continue;
                }
            }
//                if(FD_ISSET(sock,&fd_write))write(sock);
//                if(FD_ISSET(sock,&fd_exp))exp(sock);
        }
    }
    return rul;
}

void Select_Multplex::addSocket(int socket) {
    std::lock_guard l(__mutex);
    if(socket > __select_socket)
        __select_socket = socket;
    __sockets.insert(socket);
}

void Select_Multplex::eraseSocket(int socket) {
    std::lock_guard l(__mutex);
    if(socket == __select_socket && __select_socket > 1)
        __select_socket--;
    __sockets.erase(socket);
}

void Select_Multplex::close() {
    std::lock_guard l(__mutex);
    Base::net_close(__select_socket);
    for(auto p = __sockets.begin();p != __sockets.end();p++)
        Base::net_close(*p);
    __sockets.clear();
}

//Epoll Multplex

#if __linux && SYS_epoll_create

Epoll_Multplex::Epoll_Multplex(const int &count)
:MultiplexAbstract(count),__event_count(count){
    __epoll_socket = epoll_create(__event_count);
    __events = new epoll_event[__event_count];
}

int Epoll_Multplex::server(long timeout) {
    int count = epoll_wait(__epoll_socket,__events,__event_count,timeout);
    for(int i=0;i<count;i++){
        int socket = __events[i].data.fd;
        uint32_t event = __events[i].events;
        if(socket < 0)continue;
        else if(event & EPOLLIN){//read
            if(!read(socket)){
                //close
                disConnect(socket);
                continue;
            }
            //set to write
//                toWrite(socket);
//            }else if(event & EPOLLOUT){//write
//                write(socket);
//                //set to read
            toRead(socket);
        }else if(event & EPOLLERR || event && EPOLLHUP ){//error
            //close
            disConnect(socket);
        }
    }
    return count;
}

void Epoll_Multplex::addSocket(int socket) {
    __event.events = EPOLLIN | EPOLLET;
    __event.data.fd = socket;
    epoll_ctl(__epoll_socket,EPOLL_CTL_ADD,socket,&__event);
}

void Epoll_Multplex::eraseSocket(int socket) {
    __event.data.fd = socket;
    epoll_ctl(__epoll_socket,EPOLL_CTL_DEL,socket,&__event);
}

void Epoll_Multplex::close() {
    Base::net_close(__epoll_socket);
}

void Epoll_Multplex::toWrite(int socket) {
    __event.data.fd = socket;
    __event.events = EPOLLOUT | EPOLLET;
    epoll_ctl(__epoll_socket,EPOLL_CTL_MOD,socket,&__event);
}

void Epoll_Multplex::toRead(int socket) {
    __event.data.fd = socket;
    __event.events = EPOLLIN | EPOLLET;
    epoll_ctl(__epoll_socket,EPOLL_CTL_MOD,socket,&__event);
}

#endif

//IOCP_Multplex

#if WIN32

IOCP_Multplex::IOCP_Multplex(const int &count)
:MultiplexAbstract(count){
    __iocp_socket = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr,0,0);
    if(__iocp_socket == nullptr){
        std::cout<<"Multplex ERROR: CreateIoCompletionPort "<<GetLastError()<<std::endl;
    }
}

int IOCP_Multplex::server(long timeout) {
    auto rul = GetQueuedCompletionStatus(__iocp_socket
            ,&datasize,(PULONG_PTR)&perHandleData
            ,&lpoverlapped,timeout);
    //read
    perIoData = (LPPER_IO_DATA)CONTAINING_RECORD(lpoverlapped,PER_IO_DATA,overlapped);
    if(datasize == 0){//close
        disConnect(perHandleData->socket);
        GlobalFree(perHandleData);
        GlobalFree(perIoData);
    }

    read(perHandleData->socket,(int8_t*)perIoData->buffer,datasize);

    //buffer clear
    ZeroMemory(&(perIoData->overlapped), sizeof(OVERLAPPED));
    perIoData->databuf.len = 1024;
    perIoData->databuf.buf = perIoData->buffer;
    perIoData->operationtype = 0;
    DWORD flags = 0;
    DWORD recvbytes;
    WSARecv(perHandleData->socket,&(perIoData->databuf),1
            ,&recvbytes,&flags,&(perIoData->overlapped), nullptr);
    return rul;
}

void IOCP_Multplex::addSocket(int socket){
    LPPER_HANDLE_DATA io_context = new PER_HANDLE_DATA();
    io_context->socket = socket;
    CreateIoCompletionPort((HANDLE)socket,__iocp_socket,(DWORD)io_context,0);
}


#endif